#include "shaderprogram.h"

#include "stb_image_no_warnings.h"

ShaderProgram::ShaderProgram(const char* vertexPath, const char* fragmentPath)
{
    compileProgram(vertexPath, fragmentPath);
}


void ShaderProgram::compileProgram(const char* vertexPath, const char* fragmentPath)
{
    // 1. retrieve the vertex/fragment source code from filePath
    std::string vertexCode;
    std::string fragmentCode;
    std::ifstream vShaderFile;
    std::ifstream fShaderFile;
    // ensure ifstream objects can throw exceptions:
    vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
    fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
    try 
    {
        // open files
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);
        std::stringstream vShaderStream, fShaderStream;
        // read file's buffer contents into streams
        vShaderStream << vShaderFile.rdbuf();
        fShaderStream << fShaderFile.rdbuf();
        // close file handlers
        vShaderFile.close();
        fShaderFile.close();
        // convert stream into string
        vertexCode   = vShaderStream.str();
        fragmentCode = fShaderStream.str();
    }
    catch (std::ifstream::failure& e)
    {
        error(e.what());
        error("ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ");
    }
    const char* vShaderCode = vertexCode.c_str();
    const char * fShaderCode = fragmentCode.c_str();
    // 2. compile shaders
    unsigned int vertex, fragment;
    // vertex shader
    vertex = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex, 1, &vShaderCode, NULL);
    glCompileShader(vertex);
    checkCompileErrors(vertex, "VERTEX");
    // fragment Shader
    fragment = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment, 1, &fShaderCode, NULL);
    glCompileShader(fragment);
    checkCompileErrors(fragment, "FRAGMENT");
    // shader Program
    ID = glCreateProgram();
    glAttachShader(ID, vertex);
    glAttachShader(ID, fragment);
    glLinkProgram(ID);
    checkCompileErrors(ID, "PROGRAM");
    // delete the shaders as they're linked into our program now and no longer necessary
    glDeleteShader(vertex);
    glDeleteShader(fragment);
}

// utilities
// ------------------------------------------------------------------------
void ShaderProgram::setBool(const std::string &name, bool value) const
{   
    int loc =  glGetUniformLocation(ID, name.c_str());
    if(loc == -1)
        std::cout << "unknown name： " << name << std::endl;
    glUniform1i(loc, (int)value); 
}
// ------------------------------------------------------------------------
void ShaderProgram::setInt(const std::string &name, int value) const
{ 
    int loc =  glGetUniformLocation(ID, name.c_str());
    if(loc == -1)
        std::cout << "unknown name： " << name << std::endl;
    glUniform1i(loc, value); 
}
// ------------------------------------------------------------------------
void ShaderProgram::setFloat(const std::string &name, float value) const
{ 
    int loc =  glGetUniformLocation(ID, name.c_str());
    if(loc == -1)
        std::cout << "unknown name： " << name << std::endl;
    glUniform1f(loc, value); 
}

void ShaderProgram::setVec2(const std::string &name, glm::vec2 &value) const
{ 
    int loc =  glGetUniformLocation(ID, name.c_str());
    if(loc == -1)
        std::cout << "unknown name： " << name << std::endl;
    glUniform2f(loc, value[0], value[1]); 
}

void ShaderProgram::setVec4(const std::string &name, glm::vec4 &value) const
{ 
    int loc =  glGetUniformLocation(ID, name.c_str());
    if(loc == -1)
        std::cout << "unknown name： " << name << std::endl;
    glUniform4f(loc, value[0], value[1], value[2], value[3]); 
}

void ShaderProgram::setVec3(const std::string &name, glm::vec3 &value) const
{ 
    int loc =  glGetUniformLocation(ID, name.c_str());
    if(loc == -1)
        std::cout << "unknown name： " << name << std::endl;
    glUniform3f(loc, value[0], value[1], value[2]); 
}

void ShaderProgram::setVec3(const std::string &name, float value0, float value1, float value2) const
{ 
    int loc =  glGetUniformLocation(ID, name.c_str());
    if(loc == -1)
        std::cout << "unknown name： " << name << std::endl;
    glUniform3f(loc, value0, value1, value2); 
}

void ShaderProgram::setMat4(const std::string &name, glm::mat4 value) const
{ 
    int loc =  glGetUniformLocation(ID, name.c_str());
    if(loc == -1)
        std::cout << "unknown name： " << name << std::endl;
    glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(value)); 
}


void ShaderProgram::checkCompileErrors(unsigned int shader, std::string type)
    {
        int success;
        char infoLog[1024];
        if (type != "PROGRAM")
        {
            glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if (!success)
            {
                glGetShaderInfoLog(shader, 1024, NULL, infoLog);
                error("ERROR::SHADER_COMPILATION_ERROR of type: {0}\n{1}", type, infoLog);
            }
        }
        else
        {
            glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if (!success)
            {
                glGetProgramInfoLog(shader, 1024, NULL, infoLog);
                error("ERROR::PROGRAM_LINKING_ERROR of type: {0}\n{1}", type, infoLog);
            }
        }
    }